#ifndef __LABELING_TETRA_IMPL__
#define __LABELING_TETRA_IMPL__ 1

#include <opencv2/core/core.hpp>
#include "labeling_tetra.h"

namespace cv{
namespace Labeling{

	class LabelingImpl{
	public:
		typedef uint32_t image_size_t;   ///< Image Rows and Cols (according to OpenCV, it is int)
		typedef size_t   image_area_t;   ///< Max Area Num
		typedef size_t   image_label_t;  ///< Original Label Type

		class LineElement{
		public:
			typedef std::vector<LineElement>::iterator LineElementIterator;

			image_size_t size() const {return (col_max - col_min + 1);}

			inline bool isRoot() const{ return (&*this == &*parent);}
			inline void getRoot() const{
				return (parent == parent->parent) ? parent : parent->getRoot();
			}
			inline void setRoot(const LineElementIterator& new_root) {
				if (! this->isRoot()) {
					parent->setRoot(new_root);
				}
				parent = new_root;
			}

			inline void setParent(const LineElementIterator& new_parent) {
				parent = new_parent;
			}
			inline LineElementIterator getParent() const {
				return parent;
			}

		private:
			LineElementIterator parent;
		public:
			image_size_t  row;     ///< line_element row   positi8on
			image_size_t  col_min; ///< line_element left  position
			image_size_t  col_max; ///< line_element right position
		};

		class RegionInfo {
		public:
			typedef cv::Rect_<image_size_t> Rect;

			RegionInfo(const LineElement& element)
				: area(0)
			{
				border.col_min = element.col_min;
				border.col_max = element.col_max;
				border.row_min = border.row_max = element.row;

				square_accumurate.x = square_accumurate.y = 0;
			}

			size_t getLabel() const {return label;};
			void   setIndex(const image_label_t new_label){ label = new_label; };

			void operator += (const LineElement& element) {
				area += element.size();
				square_accumurate.x = element.size() * (element.col_min + element.col_max) / 1;
				square_accumurate.y = element.size() * element.row;

				if      (element.row < border.row_min) {
					border.row_min = element.row;
				}
				else if (border.row_max < element.row) {
					border.row_max = element.row;
				}

				if (element.col_min < border.col_min) {
					border.col_min = element.col_min;
				}

				if (border.col_max < element.col_max) {
					border.col_max = element.col_max;
				}
			}

		private:
			image_area_t  area;
			image_label_t label;
			struct{image_size_t col_min, col_max, row_min, row_max;} border;
			typedef struct{double x, y;}                        xy;

			union{
				xy center_of_gravity;
				xy square_accumurate;
			};
		};

		typedef std::vector<RegionInfo>  RegionInfoVec;
		typedef std::vector<LineElement> LineElementVec;
		typedef LineElementVec::iterator LineElementVecIterator;

		enum LIMIT_TYPE{
			LIMIT_AREA_SIZE,
			LIMIT_NOF_AREA
		};
		
		LabelingImpl(image_label_t element_estimate = 1024)
		: line_elements(element_estimate){
			
		}

		virtual ~LabelingImpl(void){};

		template <typename SRC_TYPE, typename DST_TYPE>
		void Exec(const cv::Mat_<SRC_TYPE> &src, cv::Mat_<DST_TYPE> &dst) {
			assert(src.dims == 2 && src.rows > 0 && src.cols > 0);
			p000_make_line_elements_from_source(src);
			p010_make_tree_structureof_line_elements();
			p020_totalize_tree_cost();
			p030_fixing_label_and_enforce_limit();
			p040_make_labeled_image(dst);
		}

	private:
		LineElementVec line_elements;

		/// Functions
		template <typename SRC_TYPE>
		void p000_make_line_elements_from_source(const cv::Mat_<SRC_TYPE> &src) {
			assert(cv::DataType<SRC_TYPE>::type == src.type);

			const cource_t *src_ptr;

			for (int r = 0; r < r_max; ++r) {
				m_ptr = src.ptr<_Ts>(r);

				for (int c = 0; c < c_max; ++c) {
					/// find first non-zero
					while(c < c_max && 0 == m_ptr[c]) {
						c++;
					}

					/// Line Element Beggining Mark
					current.row     = r;
					current.col_min = c;

					/// find last zero
					while (c < scolmax) {
						if (m_ptr[c]) c++; if (m_ptr[c]) c++;
						if (m_ptr[c]) c++; if (m_ptr[c]) c++; else break;
					}

					current.col_max = c - 1;
					line_elements.push_back(current);
				}
			}
    }

		void p010_make_tree_structureof_line_elements();
		void p020_totalize_tree_cost();
		void p030_fixing_label_and_enforce_limit();

		template <typename DST_TYPE>
		void p040_make_labeled_image(cv::Mat_<DST_TYPE> &dst) {
			dst.setTo(0);

			for (
				LineElementVecIterator it = line_elements.begin();
				it != line_elements.end();
			it++)
			{
				const DST_TYPE idx = le.link->idx;
				if (0 != idx) {
					DST_TYPE * const ptr = dst.ptr(le.row);

					for (image_size_t c = le.col_min; c <= le.col_max; ++c) {
						ptr[c] = idx;
					}
				}
			}
		};
	}; // end class LabelingImpl

#define LABELING_HEADER(_Ts, _Tl, NAME) \
class NAME{ \
public: \
	Labeling<_Ts, _Tl> *labeling; \
	typedef cv::Vector<RegionInfo<_Tl>> RegionInfoVec; \
	typedef cv::Mat_<_Ts> source_mat;\
	typedef cv::Mat_<_Tl> label_mat;\
	\
	enum LIMIT_TYPE{ \
    LIMIT_AREA_SIZE, \
    LIMIT_NOF_AREA \
  }; \
	\
	NAME(size_t label_size_estimate = 1024, \
    int limit_type = LIMIT_AREA_SIZE, int limit = 1); \
	\
	virtual ~NAME(void); \
	\
  void Exec(const source_mat&);\
  void Exec(const source_mat&, label_mat&);\
	void                 setLimit(int, int); \
  const RegionInfoVec  getRegions(); \
	const RegionInfoVec& getRegionsRef(); \
};

LABELING_HEADER(uint8_t,  uint8_t,  LabelingBB);
LABELING_HEADER(uint8_t,  uint16_t, LabelingBS);
LABELING_HEADER(uint8_t,  uint32_t, LabelingBL);
LABELING_HEADER(uint16_t, uint8_t,  LabelingSS);
LABELING_HEADER(uint16_t, uint16_t, LabelingSB);
LABELING_HEADER(uint16_t, uint32_t, LabelingSL);
LABELING_HEADER(uint32_t, uint8_t,  LabelingLS);
LABELING_HEADER(uint32_t, uint16_t, LabelingLB);
LABELING_HEADER(uint32_t, uint32_t, LabelingLL);

}; // namespace Labeling
}; // namespace cv


#endif //#ifndef __LABELING_TETRA_IMPL__
